<?php
###
#  Baing PHP 框架 1.0.0
#  http://baing.co/
#
#  Copyright: 白晓阳
#  License: CC BY-NC-SA 4.0
#  https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
###

namespace bai;

use bai\feature\BaingException, bai\feature\RequestException, bai\feature\SystemException;
use bai\feature\Agent;
use bai\resource\application\Logger;

/**
 * Runtime:
 * GET/DELETE: field1=value1&field2=value2&...
 * GET: QUERY=[field1=value1,...]&QUALITY=[...]&LINKS=[...]&FIELDS=[...]
 * POST: field1=value1&field2=value2&...
 * PATCH: QUERY=[field1=value1,...]&DATA=[...]
 */
final class Request extends Baing
{
    protected static array $getters = ['query', 'data', 'method', 'path', 'ajax'];
    protected static array $setters = ['query', 'data'];

    private static string $host = '';
    private static string $root = '';
    private static string $location = '';

    protected array $runtime = [
        'configurable' => false,
        'method' => self::GET,
        'ajax' => true,
        'path' => '/',
        'view' => '',
        'query' => [],
        'data' => [],
    ];

    public static function process()
    {
        try {
            Logger::print('== Request Start ==', Logger::INFO);
            if (!defined('DEV')) {
                ob_start();
            }
            self::$request = new Request();
            self::$response = self::loadClass('bai\Response');
            self::$logger = Logger::setup();
            self::$request->run();
            if (!defined('DEV')) {
                $notice = ob_get_clean();
                if ($notice) {
                    Logger::print($notice, Logger::WARING);
                }
            }
            Logger::print('== Request End ==', Logger::INFO);
        } catch (BaingException $e) {
            Logger::print($e->getFullMessage());
            Logger::print($e->getTraceAsString(), solo: true);
            self::$response->setStatus($e->getCode());
            self::$response->addResponse($e->export());
            Logger::print('== Request Fail ==', Logger::WARING);
        } catch (\Throwable $e) {
            Logger::print(BaingException::format($e));
            Logger::print($e->getTraceAsString(), solo: true);
            self::$response->setStatus(Response::STATUS_ERROR);
            self::$response->addResponse(BaingException::formatExport($e));
            Logger::print('== Request Fail ==', Logger::WARING);
        } finally {
            self::$response?->run();
            self::$logger = null;
            self::$response = null;
            self::$request = null;
        }
    }

    public static function url(string $path, bool $short = false): string
    {
        $base = '';
        if (!$short) {
            $base = self::$host;
        }
        if (self::$root !== '/') {
            $base .= self::$root;
        }
        if ($path[0] === '/') return $base . $path;
        return "$base/$path";
    }

    public function __get(string $name)
    {
        static $staticFields = ['host', 'root', 'location'];

        if (in_array($name, $staticFields)) {
            return self::$$name;
        }
        if (in_array($name, self::$getters)) {
            return $this->find($name);
        }
        return null;
    }

    public function run()
    {
        self::$process = Process::setup($this->find('path'));
        self::$process?->run();
        self::$process = null;
    }

    public function session(string|array $key = null, $value = null)
    {
        return Agent::session($key, $value);
    }

    protected function init()
    {
        $requestUri = parse_url(Agent::server('REQUEST_URI'), PHP_URL_PATH);
        $requestScript = Agent::server('SCRIPT_NAME');
        if ($requestScript !== $requestUri && str_ends_with($requestScript, $requestUri)) {
            $requestScript = substr($requestScript, 0, - strlen($requestUri));
        }

        self::$root = str_replace('\\', '/', dirname($requestScript));
        self::$location = str_replace('\\', '/', dirname(Agent::server('SCRIPT_FILENAME')));
        $https = Agent::server('HTTPS');
        $host = (!$https || !strcasecmp($https, 'off')) ? 'http://' : 'https://';
        $host .= Agent::server('SERVER_NAME');
        if (($port = Agent::server('SERVER_PORT')) && $port != 80 && $port != 443) {
            $host .= ':' . $port;
        }
        self::$host = $host;

        $method = Agent::server('REQUEST_METHOD');
        if (!in_array($method, self::REST)) {
            throw new RequestException('method', $method);
        }
        if ($method === self::PUT) {
            $method = self::PATCH;
        }
        $this->runtime['method'] = $method;
        $path = Agent::server('PATH_INFO');
        if (!$path && self::$root === '/') {
            $path = $requestUri;
        }
        $this->runtime['path'] = $path ?: '/';
        $this->runtime['ajax'] = strcasecmp(Agent::server('HTTP_X_REQUESTED_WITH'), 'XMLHttpRequest') === 0;

        $this->parseData();
    }

    private function parseData()
    {
        $this->runtime['query'] = $query = Agent::get();
        $this->runtime['data'] = $data = Agent::post();

        $method = $this->runtime['method'];
        if ($method === self::POST || $method === self::PATCH) {
            if (!$data && $input = file_get_contents('php://input')) {
                $contentType = Agent::server('CONTENT_TYPE') ?? '';
                if (stripos($contentType, 'application/json') === false) {
                    throw new RequestException('contentType', $contentType);
                }
                $this->runtime['data'] = $data = json_decode($input, true) ?? [];
            }
        }

        if ($method === self::POST && ($customMethod = $this->runtime['data']['METHOD'] ?? '')) {
            if (in_array($customMethod, self::REST)) {
                $this->runtime['method'] = $method = $customMethod;
                if ($method === self::GET || $method == self::DELETE) {
                    $this->runtime['query'] = $query = $data;
                    $this->runtime['data'] = $data = [];
                }
            }
        }

        if ($method === self::GET || $method === self::DELETE) {
            $list = ['query' => 'QUERY', 'quality' => 'QUALITY', 'links' => 'LINKS', 'fields' => 'FIELDS'];
            foreach ($list as $key => $alias) {
                if (isset($query[$alias])) {
                    $this->runtime[$key] = json_decode($query[$alias], true) ?? [];
                }
            }
            return;
        }

        if ($method === self::POST || $method === self::PATCH) {
            $list = ['data' => 'DATA', 'query' => 'QUERY', 'quality' => 'QUALITY', 'links' => 'LINKS'];
            foreach ($list as $key => $alias) {
                if (isset($data[$alias])) {
                    $this->runtime[$key] = $data[$alias];
                }
            }
        }
    }
}


set_error_handler(function(int $code, string $message, string $file, int $line) {
    Logger::print("$file($line): $message");
    throw new SystemException(params: $message);
});
